package device;

import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.PathParam;

import device.DeviceFactory;


/**
 * @ServerEndpoint 注解是一个类层次的注解，它的功能主要是将目前的类定义成一个websocket服务器端,
 * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
 */
@ServerEndpoint("/dws/{param}")
public class DeviceEndpointServlet {
    //静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

    // 每一个设备终端的MAC地址作为键值
    private static HashMap<String, DeviceEndpointServlet> DeviceMap= new HashMap<>();

    //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;
    
    // MAC地址
    private String  mac = null;
    
    /**
     * 将要由系统关闭
     */
    private boolean _removed = false;
    
    // 测试默认设备ID
    private static int DEFAULT_DEVICE_ID = 1;
    
	/**
	 * 字节数组分割
	 * @param src
	 * @param c
	 * @return
	 */
	public static ArrayList<byte[]> split(byte[] src, char c) {
		ArrayList<byte[]> list = new ArrayList<>();
		int head = 0, i = 0, length = src.length;
		while(true) {
			if(src[i] == c) {
				if(head < i) {
					byte[] part = new byte[i - head];
					for(int j = head; j < i; j++) {
						part[j - head] = src[j];
					}
					list.add(part);
				}
				head = i + 1;
			}
			else if(i == length - 1) {
				if(head <= i) {
					byte[] part = new byte[i - head + 1];
					for(int j = head; j <= i; j++) {
						part[j-head] = src[j];
					}
					list.add(part);
				}
				break;
			}
			i++;
			if(i >= src.length) {
				break;
			}
		}
		
		return list;
	}
	
	/**
	 * 字节数组格式化为 <属性id, 值> 的键值对
	 * @param list
	 * @return
	 */
	public static HashMap<Integer, String> formatAttribute(ArrayList<byte[]> list) {
		// 转化为String HashMap
		HashMap<Integer, String> sites = new HashMap<Integer, String>();
		StringBuffer sBuffer = new StringBuffer();
		for(int i =0; i< list.size(); i++) {
			byte[] part = list.get(i);
			sBuffer.setLength(0);
			if(part.length >1) {
				for(int j = 1; j <part.length; j++) {
					sBuffer.append((char)part[j]);
				}
				sites.put((int)part[0], sBuffer.toString());
			}
		}
		return sites;
		
	}
	
	
	public void replaced()  {
		DeviceMap.remove(mac);
		this._removed = true;
		try {
			this.session.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		}
	}
	
    /**
     * 连接建立成功调用的方法
     * @param session  可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("param")String param){
    	System.out.println(String.format("\n[设备请求连接]MAC=%s, sessionId=%s", param, session.getId()));
    	this.session = session;
    	
    	
    	if(param == null) {
    		this.replaced();
    		return;
    	}
        
        this.mac = param;
        
        DeviceEndpointServlet endpointServlet = DeviceMap.get(mac);
        if(endpointServlet != null) {
        	endpointServlet.replaced();
        	System.out.println(String.format("\n设备[%s]重连", mac));
        	//endpointServlet.;
        }
        else {
             addOnlineCount();           //在线数加1
             System.out.println(String.format("\n设备[%s]上线,当在线设备数量:%d", mac, getOnlineCount()));
          // 设备组上线
             DeviceFactory.setOnline(DEFAULT_DEVICE_ID, true);
        }
        DeviceMap.put(mac, this);
        
        //设置session非活跃时生命周期
        session.setMaxIdleTimeout(10000);
        //System.out.println("HashMapSize=" + DeviceMap.size());
        
       
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(){
    	if(_removed) {
    		return;
    	}
        DeviceMap.remove(mac);
        subOnlineCount();           //在线数减1
        System.out.println(String.format("\n设备[%s]下线,当前在线设备:%d", mac, getOnlineCount()));
     // 设备组下线
        DeviceFactory.setOnline(DEFAULT_DEVICE_ID, false);
        this._removed = true;
    }
    
    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session 可选的参数
     */
    @OnMessage
    public void onMessage(String message, Session session) {
    	byte[] buffer = message.getBytes();
    	
    	System.out.print(String.format("\n收到设备[%s]数据:", mac));
    	System.out.print('{');
    	for(int i = 0; i <buffer.length; i++) {
    		System.out.print((int)buffer[i]);
    		System.out.print(',');
    	}
    	System.out.print('}');
    	
    	int length = buffer.length;
    	if(length <3) {
    		return;
    	}
    	int deviceId = buffer[0];
    	int messageType = buffer[1];
    	
    	switch (messageType) {
		case 1:	// 电源管理
			if(length > 2) {
				DeviceFactory.setPower(deviceId, (buffer[2] == 1));
			}
			break;
		case 2: // 属性更新
			if(length > 3) {
				byte[] attrData = new byte[length - 2];
				System.arraycopy(buffer, 2, attrData, 0, length - 2);
				// 转化为String HashMap
				HashMap<Integer, String> sites = formatAttribute(split(attrData, ','));
				
				DeviceFactory.setAttribute(deviceId, sites);
			}
			

		default:
			break;
		}
    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        //System.out.println("发生错误");
        //error.printStackTrace();
        if(_removed) {
        	return;
        }
        int count = 0;
        Throwable root = error;
        while (root.getCause() != null && count < 20) {
            root = root.getCause();
            count ++;
        }
        if (root instanceof EOFException) {
        	// 无视
        } 
        else {
        	//error.printStackTrace();
        	System.out.println(String.format("设备[%s]连接发生错误, 错误:%s", mac, error.getLocalizedMessage()));
        	try {
        		session.close();
        	}
        	catch (Exception e) {
				// TODO: handle exception
			}
        	
        }
    }

    /**
     * 这个方法与上面几个方法不一样。没有用注解，是根据自己需要添加的方法。
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);
    }
    
    
    /**
     * 广播
     * @para message
     */
    public static synchronized void broadcast(String message) {
    	//群发消息
    	for (String k : DeviceMap.keySet()){
            try {
                DeviceMap.get(k).sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
	}

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        DeviceEndpointServlet.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        DeviceEndpointServlet.onlineCount--;
    }
}

